namespace RL

open Garnet.Composition
open RL.Components
open RL.Mapping
open System.Collections.Generic

module Pathfinding =
    let GetBlockingPositions(world : Container) =
        world.Query<Point2D, BlocksMovement>()
        |> Seq.map (fun e -> e.Value1)
        |> Seq.toList

    let GetBlockingPositionsExcept(world : Container, except : Eid) =
        world.Query<Point2D, BlocksMovement, Eid>()
        |> Seq.filter (fun e -> e.Value3 <> except)
        |> Seq.map (fun e -> e.Value1)
        |> Seq.toList

    let GetBlockingEntityAt(world : Container, position : Point2D) =
        let matchOpt e =
            match e with
                | Some(e) -> Some(world.Get(e))
                | None -> None
        world.Query<Eid, Point2D, BlocksMovement>()
        |> Seq.filter (fun e -> e.Value2 = position)
        |> Seq.map (fun e -> e.Value1)
        |> Seq.tryExactlyOne
        |> matchOpt

    type DijkstraMap(w : int, h : int, maxD : single) =
        let width = w
        let height = h
        let maxDepth = maxD
        let dmap = [|for _ in 0 .. width * height do yield 3.4028235E+38f|]
        member _.Map = dmap

        member _.Clear() = for i in 0 .. dmap.Length - 1 do dmap[i] <- 3.4028235E+38f

        member _.Build(starts : int list, blocks : int list, map : RLMap) =
            let openList = Queue<int * float32>()
            for start in starts do
                openList.Enqueue((start, 0.0f))

            while openList.Count > 0 do
                let index, depth = openList.Dequeue()
                let exits = map.GetAvailableExits(index, blocks)
                for newIndex, addDepth in exits do
                    let newDepth = depth + addDepth
                    let prevDepth = dmap[newIndex]
                    if not (newDepth >= prevDepth) && not (newDepth >= maxDepth) then
                        dmap[newIndex] <- newDepth
                        openList.Enqueue((newIndex, newDepth))

        member _.FindLowestExit(pos : int, blocks : int list, map : RLMap) =
            let exits = map.GetAvailableExits(pos, blocks)
            if exits.IsEmpty then
                None
            else
                let comparison (e1 : int, _ : single) (e2 : int, _ : single) = compare dmap[e1] dmap[e2]
                let exits = exits |> List.sortWith comparison
                Some(exits[0])
